<?php

namespace liketp\foundation;

use liketp\exception\Exception;
use liketp\Log;
use Throwable;

class Timer
{
    protected static $init = false;
    protected static $timerId = 0;
    protected static $timerList;
    protected static $maxSleep = 10 * 1000;
    protected static $removeList = [];
    protected const RATE = 1000 * 1000;

    public function init(string $queueName = '')
    {
        if (static::$init) return;
        static::$init = true;

        $queueName = $queueName ?: 'liketp\foundation\PriorityQueue';
        static::$timerList = new $queueName();
    }

    public function add(float $delay, $fn, array $params = [], bool $persistant = false)
    {
        if (!static::$init) $this->init();

        if ($delay <= 0) throw new Exception('The time of timer is must great then zero.');
        if ($delay * static::RATE < static::$maxSleep) throw new Exception('The time of timer is to short.');

        $timer = $this->createTimer($delay, $fn, $params, $persistant);
        static::$timerList->insert($timer, $timer['trigger_time']);

        return $timer['id'];
    }

    public function remove($id)
    {
        if (isset(static::$removeList[$id]) == false) {
            static::$removeList[$id] = true;
        }
    }

    public function run()
    {
        $this->init();

        while (true) {

            if (($delay = $this->recentTriggerTime(true)) > 0) {
                usleep(min($delay, static::$maxSleep));
            } else {

                $this->tick();
            }
        }
    }

    public function isEmpty()
    {
        return static::$timerList->isEmpty();
    }

    public function recentTriggerTime(bool $returnDelay = false)
    {
        if ($this->isEmpty()) return PHP_INT_MAX;

        $top = static::$timerList->top();

        return $returnDelay ? ($top['trigger_time'] - microtime(true)) * static::RATE : $top['trigger_time'];
    }

    public function tick()
    {
        while ($this->recentTriggerTime(true) <= 0) {

            $task = static::$timerList->extract();

            if ($task && $this->timerCheck($task)) {
                try {
                    call_user_func_array($task['fn'], $task['params']);

                    if ($task['persistant']) {
                        $task['trigger_time'] = $task['trigger_time'] + $task['delay'];
                        static::$timerList->insert($task, $task['trigger_time']);
                    }
                } catch (Throwable $e) {

                    Log::record('Timer Exception: ' . $e->getMessage(), 'error');
                }
            }
        }
    }


    //------------------------------------------------ protected
    protected function createTimer($delay, $fn, $params, $persistant)
    {
        $time = microtime(true) + $delay;
        $timerId = static::$timerId++;
        return [
            'id' => $timerId,
            'trigger_time' => $time,
            'delay' => $delay,
            'fn' => $fn,
            'params' => $params,
            'persistant' => $persistant
        ];
    }

    protected function timerCheck($timer)
    {
        $id = $timer['id'] ?? -1;
        if (isset(static::$removeList[$id])) {
            unset(static::$removeList[$id]);
            return false;
        } else {
            return true;
        }
    }
}
